/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2012-2014 ForgeRock AS.
 */
package org.opends.server.snmp;

import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.management.Attribute;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.security.auth.Subject;

import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.jmx.Credential;
import org.opends.server.protocols.jmx.OpendsJmxPrincipal;

import com.sun.management.snmp.SnmpStatusException;

/**
 * The SNMPMonitor Class allows to get a singleton SNMPMonitor object allowing
 * to access the JMX cn=monitor MBean.
 */
public class SNMPMonitor
{

  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  /**
   * Singleton SNMPMonitor object.
   */
  private static SNMPMonitor monitor = null;
  /**
   * Monitor MBeanServer server.
   */
  private MBeanServer server;
  /**
   * Subject Auth to use to access the JMX Mbeans cn=monitor.
   */
  private Subject subject = null;
  /**
   * Pattern to use to query the cn=monitor MBeans.
   */
  public static ObjectName pattern;

  static
  {
    try
    {
      pattern =
          new ObjectName(SNMPConnectionHandlerDefinitions.JMX_DOMAIN
              + "Name=rootDSE,Rdn1=cn-monitor,*");
    }
    catch (Exception ex)
    {
      logger.traceException(ex);
    }
  }

  /**
   * Creates an SNMPMonitor object mapping.
   *
   * @param server
   *          to use to the mapping
   */
  private SNMPMonitor(MBeanServer server)
  {
    this.server = server;
    this.subject = new Subject();
    this.subject.getPrincipals().add(new OpendsJmxPrincipal("cn=anonymous"));
    InternalClientConnection clientConnection =
        InternalClientConnection.getRootConnection();
    this.subject.getPrivateCredentials().add(new Credential(clientConnection));
  }

  /**
   * Gets the singleton SNMPMonitor object.
   *
   * @param server
   *          The server
   * @return the SNMPMonitor mapping object.
   */
  public static SNMPMonitor getMonitor(MBeanServer server)
  {
    if (monitor == null)
    {
      monitor = new SNMPMonitor(server);
    }
    return monitor;
  }

  /**
   * Gets the Connection Handlers Statistics MBean.
   *
   * @return the Set of Connection Handlers Statistics. If Statistics do not
   *         exist then an empty Set is returned
   */
  public Set<ObjectName> getConnectionHandlersStatistics()
  {
    Set<ObjectName> results = new HashSet<ObjectName>();
    try
    {
      Set monitorObjects = this.server.queryNames(SNMPMonitor.pattern, null);
      for (Iterator iter = monitorObjects.iterator(); iter.hasNext();)
      {
        ObjectName name = (ObjectName) iter.next();
        if ((name.getCanonicalName().contains("Connection_Handler"))
            && (name.getCanonicalName().endsWith("_Statistics")))
        {
          results.add(name);
        }
      }
    }
    catch (Exception ex)
    {
      logger.traceException(ex);
    }
    return results;
  }

  /**
   * Return the ObjectName of the Connection Handler corresponding to the
   * statistics name.
   *
   * @param statistics
   *          ObjectName
   * @return the Connection Handler ObjectName, null otherwise
   */
  public ObjectName getConnectionHandler(ObjectName statistics)
  {

    // Check parameter
    if (statistics == null)
    {
      return null;
    }

    try
    {
      String value = statistics.getCanonicalName();
      if (!value.endsWith("_Statistics"))
      {
        return null;
      }
      int index = value.indexOf("_Statistics");
      String name = value.substring(0, index);
      ObjectName connectionHandlerName = new ObjectName(name);

      // Check if the MBean exists
      Set query = this.server.queryNames(connectionHandlerName, null);
      if ((query != null) && (!query.isEmpty()))
      {
        return connectionHandlerName;
      }
    }
    catch (Exception ex)
    {
      logger.traceException(ex);
    }
    return null;
  }

  /**
   * Return a Set of Connection Handler ObjectNames.
   *
   * @return the Set of ObjectNames, an empty Set if no connection handlers
   */
  public Set<ObjectName> getConnectionHandlers()
  {
    Set monitorObjects;
    Set<ObjectName> results = new HashSet<ObjectName>();
    try
    {
      monitorObjects = this.server.queryNames(SNMPMonitor.pattern, null);
      for (Iterator iter = monitorObjects.iterator(); iter.hasNext();)
      {
        ObjectName name = (ObjectName) iter.next();
        if ((name.getCanonicalName().contains("Connection_Handler"))
            && (!(name.getCanonicalName().endsWith("_Statistics")))
            && (name.getKeyProperty("Rdn3") == null))
        {
          results.add(name);
        }
      }
      return results;
    }
    catch (Exception ex)
    {
      logger.traceException(ex);
    }
    return results;
  }

  /**
   * Returns the ObjectName of the Statistics Connection Handler name.
   * corresponding to the Connection Handler name
   *
   * @param connectionHandlerName
   *          The connection handler name
   * @return the ObjectName of the statistics ObjectName, null if the statistics
   *         could not be found
   */
  public ObjectName getConnectionHandlerStatistics(
      ObjectName connectionHandlerName)
  {

    if (connectionHandlerName == null)
    {
      return null;
    }
    try
    {
      String value =
          connectionHandlerName.getCanonicalName().concat("_Statistics");
      ObjectName statistics = new ObjectName(value);
      // Check if the MBean exists
      Set query = this.server.queryNames(statistics, null);
      if ((query != null) && (!query.isEmpty()))
      {
        return statistics;
      }
    }
    catch (Exception ex)
    {
      logger.traceException(ex);
    }
    return null;
  }

  /**
   * Get the value of the attribute.
   *
   * @param name
   *          of Mbean as a String
   * @param attribute
   *          to look for
   * @return the value of the attribute, null if the attribute could not be
   *         found
   */
  public Object getAttribute(String name, String attribute)
  {
    try
    {
      ObjectName objName =
          new ObjectName(SNMPConnectionHandlerDefinitions.JMX_DOMAIN + "Name="
              + name);
      return getAttribute(objName, attribute);
    }
    catch (Exception ex)
    {
      logger.traceException(ex);
    }
    return null;
  }

  /**
   * Gets the value of an attribute.
   *
   * @param name
   *          of the Mbean
   * @param attribute
   *          to look for
   * @return the value of the attribute, null if the attribute value could not
   *         be found
   */
  @SuppressWarnings("unchecked")
  public Object getAttribute(final ObjectName name, final String attribute)
  {
    return Subject.doAs(this.subject, new PrivilegedAction()
    {

      public Object run()
      {
        try
        {
          Attribute attr = (Attribute) server.getAttribute(name, attribute);
          if (attr != null)
          {
            return attr.getValue();
          }
        }
        catch (Exception ex)
        {
          logger.traceException(ex);
        }
        return null;
      }
    });
  }

  /**
   * Wrapper for SNMP Byte[].
   *
   * @param s
   *          value string
   * @return a Byte[]
   */
  public static Byte[] string2ByteArray(String s)
  {
    byte[] b = s.getBytes();
    Byte[] barray = new Byte[b.length];
    for (int index = 0; index < b.length; index++)
    {
      barray[index] = new Byte(b[index]);
    }
    return barray;
  }

  /**
   * Wrapper for SNMP Counter32.
   *
   * @param v
   *          value
   * @return a counter32
   */
  public static long counter32Value(long v)
  {
    if (v > (pow(2, 32) - 1))
    {
      return (v % pow(2, 32));
    }
    else
    {
      return v;
    }
  }

  /**
   * Wrapper for SNMP Counter32.
   *
   * @param V
   *          Value
   * @return a Counter32
   */
  public static Long counter32Value(Long V)
  {
    long v = V.longValue();
    if (v > (pow(2, 32) - 1))
    {
      return new Long(v % pow(2, 32));
    }
    else
    {
      return V;
    }
  }

  /**
   * Latcher for SNMP Gauge32.
   *
   * @param v
   *          value
   * @return a gauge32
   */
  public static long gauge32Value(long v)
  {
    if (v > (pow(2, 32) - 1))
    {
      return (pow(2, 32) - 1);
    }
    else
    {
      return v;
    }
  }

  /**
   * Latcher for SNMP Gauge32.
   *
   * @param V
   *          value
   * @return a gauge32
   */
  public static Long gauge32Value(Long V)
  {
    long v = V.longValue();
    if (v > (pow(2, 32) - 1))
    {
      return new Long(pow(2, 32) - 1);
    }
    else
    {
      return V;
    }
  }

  /**
   * Checker for SNMP INTEGER.
   *
   * @param V
   *          value
   * @return an Integer
   * @throws com.sun.management.snmp.SnmpStatusException
   *           If an error occurs
   */
  public static Integer integerValue(Long V) throws SnmpStatusException
  {
    long v = V.longValue();
    if (v > (pow(2, 31) - 1))
    {
      throw new SnmpStatusException("Returned intrumented value size too big");
    }
    Integer ret = new Integer(V.intValue());
    return ret;
  }

  /**
   * pow x^y.
   */
  private static long pow(long x, long y)
  {
    int j = 1;
    long k = x;
    if (y == 0)
    {
      return 1;
    }
    if (y == 1)
    {
      return x;
    }
    while (j < y)
    {
      k = k * x;
      j++;
    }
    return k;
  }
}
